/*
 * GameManager.cpp
 *
 *  Created on: Feb 10, 2010
 *      Author: Daniel Crown
 */

#include "GameManager.h"
#include "BasicWindow.h"
#include "Logger.h"
#include "..\Includes\Common.h"
#include "..\Graphics\DXManager.h"
#include "..\Systems\ResourceManager.h"

extern BasicWindow* gpBW;
extern DXManager* gpDX;
extern ResourceManager* gpRM;

const int PROJECTILE_PLANE_X_OFFSET = 10;
const int PROJECTILE_PLANE_Y_OFFSET = 4;
const int MAX_NO_SLEEP_ITERATIONS = 5;
const int MAX_SKIPPED_FRAMES = 5;
const int STATS_INTERVAL_MS = 10000;
const int MILLIS_PER_SECOND = 1000;
const int NUM_SAVED_FPS = 10;


void GameManager::Create()
{
	plane.Create(START_POS_X, gpBW->GetHeight()/2);

	gameClock.Start();
	playClock.Start();

	gpRM->OpenFile("..\\resources\\movePatterns\\test.mp");
	baddies.push_back(gpRM->ReadBaddie("..\\resources\\movePatterns\\test.mp"));
	gpRM->CloseFile("..\\resources\\movePatterns\\test.mp");

	//create all these up front - probably only need one copy of this, but this is fine for now
	for (int i = 0; i < MAX_PROJECTILES; i++)
	{
		proj[i].Create();
	}

	bg.Create();
	nextProj = 0;
	//initialize loop variables
	oversleepTime_ms = excessURTime_ms = tiredCounter = 0;

	//initialize stats vars
	avgFPS = avgUPS = 0;

	Logger::getInstance().LOG(Logger::DEBUG, "GameManager Created");
}

//----------------------------------------------------------------------------------------------------------------------
//Update/Render pair
//----------------------------------------------------------------------------------------------------------------------
void GameManager::Update()
{
	DWORD time = gameClock.GetTime();
	plane.Update();
	bg.Update();
	for (unsigned int i = 0; i < MAX_PROJECTILES; i++)
	{
		proj[i].Update();
		if (baddies.size() > i)
		{
			baddies[i]->Update(time);
		}
	}
}

void GameManager::Render()
{
	gpDX->BeginRender();
	bg.Render();
	plane.Render();

	//projectiles go on top of plane
	for (unsigned int i = 0; i < MAX_PROJECTILES; i++)
	{
		proj[i].Render();
		if (baddies.size() > i)
		{
			baddies[i]->Render();
		}
	}
	gpDX->EndRender();
}

//----------------------------------------------------------------------------------------------------------------------
//Handle Plane input
//----------------------------------------------------------------------------------------------------------------------
void GameManager::MovePlane(Direction d)
{
	//Logger::getInstance().LOG(Logger::DEBUG, "Move Plane");
	plane.Move(d);
}

void GameManager::PlaneFire()
{
	//if nextProj is visible, there are already MAX_PROJECTILES on the screen
	if (!proj[nextProj].isVisible())
	{
		proj[nextProj].Activate(plane.GetXPos()+PROJECTILE_PLANE_X_OFFSET, plane.GetYPos()+PROJECTILE_PLANE_Y_OFFSET);
		plane.Fire();
		nextProj = (nextProj+1)%MAX_PROJECTILES;
		//Logger::getInstance().LOG(Logger::DEBUG, "NextProj %d", nextProj);
	}
}

//----------------------------------------------------------------------------------------------------------------------
//Game Loop
//----------------------------------------------------------------------------------------------------------------------
void GameManager::Loop()
{
	//mark the current time in the gameClock
	gameClock.Mark("loop", Clock::NA);

	//first, see if we are falling behind on Updates/Sec
	int skips = 0;
	while ((excessURTime_ms > PERIOD_MS) && (skips < MAX_SKIPPED_FRAMES))
	{
		//Logger::getInstance().LOG(Logger::DEBUG, "Non-rendered update");
		//if yes, update without rendering
		skips++;
		excessURTime_ms -= PERIOD_MS;
		Update();
	}
	totalSkippedFrames += skips;

	//Logger::getInstance().LOG(Logger::DEBUG, "Beginning Update/Render");
	//update and render
	Update();
	Render();

	//urTime = time we spent on Update and Render
	urTime_ms = (short)(gameClock.GetTimeSinceMark("loop", Clock::REMOVE_MARK));
	//Logger::getInstance().LOG(Logger::DEBUG, "Update/Render complete: %d ms", urTime_ms);
	//Logger::getInstance().LOG(Logger::DEBUG, "oversleepTime = %d ms", oversleepTime_ms);

	//timeToKill is the period minus the amount of time we
	//already spent processing (urTime), minus
	//the amount of time we overslept last cycle
	timeToKill_ms = (short)((PERIOD_MS - urTime_ms) - oversleepTime_ms);

	//if there is time left in this cycle, sleep it off
	if (timeToKill_ms > 0)
	{
		//Logger::getInstance().LOG(Logger::DEBUG, "Beginning TTK Sleep: %d ms", timeToKill_ms);
		gameClock.Mark("sleep", Clock::NA);
		Sleep(timeToKill_ms);

		//oversleepTime = time we did sleep minus the time we should have slept
		oversleepTime_ms = gameClock.GetTimeSinceMark("sleep", Clock::REMOVE_MARK) - timeToKill_ms;
		//Logger::getInstance().LOG(Logger::DEBUG, "Return from TTK Sleep; Oversleep = %d ms", oversleepTime_ms);

	} else {
		//Logger::getInstance().LOG(Logger::DEBUG, "No TTK Sleep");
		//else the update/render took longer than a period to execute
		//store the amount of time we ran over (-= because timeToKill is neg)
		excessURTime_ms -= timeToKill_ms;
		oversleepTime_ms = 0;

		//if we keep running too long and not sleeping, yield now
		if (++tiredCounter > MAX_NO_SLEEP_ITERATIONS) {
			//Logger::getInstance().LOG(Logger::DEBUG, "Ran too long with no sleep, yielding");
			tiredCounter = 0;
			Sleep(0);
			//Logger::getInstance().LOG(Logger::DEBUG, "Return from yield");
		}
	}

	StoreStats();
}

void GameManager::StoreStats() {

	static int frameCount = 0;
	static int statsCount = 0;
	static int timeSinceLastStats = 0;
	static float fpsStore[NUM_SAVED_FPS];
	static float upsStore[NUM_SAVED_FPS];

	frameCount++;
	timeSinceLastStats += PERIOD_MS;

	short elapsedStatsTime = (short)(gameClock.GetTimeSinceMark("stats", Clock::LEAVE_MARK));
	//Logger::getInstance().LOG(Logger::DEBUG, "elapsedStatsTime: %d ms", elapsedStatsTime);
	if (elapsedStatsTime >= STATS_INTERVAL_MS)
	{
		int gameTime = gameClock.GetTime();
		float timingError = (((float)(elapsedStatsTime - timeSinceLastStats))/(float)timeSinceLastStats)*100;
		float actualFPS = 0.0, totalFPS = 0.0;
		float actualUPS = 0.0, totalUPS = 0.0;

		if (gameTime > 0)
		{
			actualFPS = ((float)frameCount/(float)gameTime)*MILLIS_PER_SECOND;
			actualUPS = ((float)(frameCount+totalSkippedFrames)/(float)gameTime)*MILLIS_PER_SECOND;
		}

		fpsStore[statsCount%NUM_SAVED_FPS] = actualFPS;
		upsStore[statsCount%NUM_SAVED_FPS] = actualUPS;
		statsCount++;

		for (int i = 0; i < NUM_SAVED_FPS; i++)
		{
			totalFPS += fpsStore[i];
			totalUPS += upsStore[i];
		}

		if (statsCount < NUM_SAVED_FPS) { // obtain the average FPS and UPS
			avgFPS = totalFPS/statsCount;
			avgUPS = totalUPS/statsCount;
		} else {
			avgUPS = totalFPS/NUM_SAVED_FPS;
			avgUPS = totalUPS/NUM_SAVED_FPS;
		}

		Logger::getInstance().LOG(Logger::DEBUG, "Game Time: %d ms", gameTime);
		Logger::getInstance().LOG(Logger::DEBUG, "Nominal Time Since Last Stats: %d ms", timeSinceLastStats);
		Logger::getInstance().LOG(Logger::DEBUG, "Real Time Since Last Stats: %d ms", elapsedStatsTime);
		Logger::getInstance().LOG(Logger::DEBUG, "Timing Error: %f\%", timingError);
		Logger::getInstance().LOG(Logger::DEBUG, "Latest U/R Time: %d ms", urTime_ms);
		Logger::getInstance().LOG(Logger::DEBUG, "Total Frame Count: %d", frameCount);
		Logger::getInstance().LOG(Logger::DEBUG, "Average FPS: %f", avgFPS);
		Logger::getInstance().LOG(Logger::DEBUG, "Average UPS: %f", avgUPS);

		gameClock.Mark("stats", Clock::UPDATE_MARK);
		timeSinceLastStats = 0;   // reset
	}
}
